package com.greensam.pixelengine.facade.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.json.JSONUtil;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.greensam.pixelengine.api.aliyunai.model.CreateOutPaintingTaskRequest;
import com.greensam.pixelengine.api.aliyunai.model.CreateOutPaintingTaskResponse;
import com.greensam.pixelengine.api.aliyunai.model.GetOutPaintingTaskResponse;
import com.greensam.pixelengine.api.aliyunai.sub.AliYunAiApi;
import com.greensam.pixelengine.api.imagesearch.ImageSearchApiFacade;
import com.greensam.pixelengine.api.imagesearch.model.ImageSearchResult;
import com.greensam.pixelengine.constant.BaseConstant;
import com.greensam.pixelengine.constant.KeyValueStorageConstant;
import com.greensam.pixelengine.constant.SpaceUserPermissionConstant;
import com.greensam.pixelengine.exception.BusinessException;
import com.greensam.pixelengine.exception.ResultCode;
import com.greensam.pixelengine.exception.ThrowUtils;
import com.greensam.pixelengine.facade.PictureFacadeService;
import com.greensam.pixelengine.manager.auth.StpKit;
import com.greensam.pixelengine.manager.upload.FilePictureUpload;
import com.greensam.pixelengine.manager.upload.PictureUploadTemplate;
import com.greensam.pixelengine.manager.upload.UrlPictureUpload;
import com.greensam.pixelengine.pojo.base.DeleteRequest;
import com.greensam.pixelengine.pojo.base.PaginationResponse;
import com.greensam.pixelengine.pojo.dto.picture.PictureUploadDto;
import com.greensam.pixelengine.pojo.emuns.ErrorCodeEnum;
import com.greensam.pixelengine.pojo.emuns.PicturePixelEnum;
import com.greensam.pixelengine.pojo.emuns.ReviewStatusEnum;
import com.greensam.pixelengine.pojo.emuns.StatusEnum;
import com.greensam.pixelengine.pojo.entity.PicturePo;
import com.greensam.pixelengine.pojo.entity.SpacePo;
import com.greensam.pixelengine.pojo.query.PicturePageQuery;
import com.greensam.pixelengine.pojo.vo.picture.request.*;
import com.greensam.pixelengine.pojo.vo.picture.response.PictureVo;
import com.greensam.pixelengine.service.PictureDataService;
import com.greensam.pixelengine.service.RoleDataService;
import com.greensam.pixelengine.service.SpaceDataService;
import com.greensam.pixelengine.utils.ColorSimilarUtils;
import com.greensam.pixelengine.utils.DataUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import org.springframework.web.multipart.MultipartFile;

import java.awt.*;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author Macro_Ray
 * @since 2025/7/2 13:39
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class PictureFacadeServiceImpl implements PictureFacadeService {

    private final RoleDataService roleDataService;
    private final PictureDataService pictureDataService;
    private final SpaceDataService spaceDataService;
    private final FilePictureUpload filePictureUpload;
    private final UrlPictureUpload urlPictureUpload;
    private final AliYunAiApi aliYunAiApi;

    private final Cache<String, String> LOCAL_CACHE =
            Caffeine.newBuilder()
                    .initialCapacity(1024)
                    .maximumSize(10000L)
                    // 缓存 5 分钟移除
                    .expireAfterWrite(5L, TimeUnit.MINUTES)
                    .build();


    @Override
    public PaginationResponse<PictureVo> pagePicture(PicturePageRequest request, Long userId) {
        // 防止爬虫
        ThrowUtils.throwIf(request.getPageSize() > 20, ErrorCodeEnum.ParamValidError);

        PicturePageQuery pageQuery = BeanUtil.copyProperties(request, PicturePageQuery.class);
        if (Objects.nonNull(request.getQueryPixel())) {
            pageQuery.setQueryPixel(PicturePixelEnum.getEnumByValue(request.getQueryPixel()));
        }
        Long spaceId = request.getSpaceId();
        if (spaceId == null) {
            // 公共图库，普通用户只能看到已过审的图片
            pageQuery.setReviewStatus(ReviewStatusEnum.APPROVED);
        } else {
            // 私有图库需要校验空间权限，已改为编程式鉴权
//            checkSpaceAuth(spaceId, userId);
            boolean hasPermission = StpKit.SPACE.hasPermission(SpaceUserPermissionConstant.PICTURE_VIEW);
            ThrowUtils.throwIf(!hasPermission, ResultCode.NO_AUTH_ERROR);
        }
        return getPictureVoPaginationResponse(pageQuery);
    }

    @Deprecated
    @Override
    public PaginationResponse<PictureVo> pagePictureWithCache(PicturePageRequest request) {
        // 防止爬虫
        ThrowUtils.throwIf(request.getPageSize() > 20, ErrorCodeEnum.ParamValidError);

        // 构建key
        String condition = JSONUtil.toJsonStr(request);
        String hashKey = DigestUtils.md5DigestAsHex(condition.getBytes());
        String key = String.format(KeyValueStorageConstant.PICTURE_INFO, hashKey);

        // 1 查询本地缓存（Caffeine）
        String pictureVoCache = LOCAL_CACHE.getIfPresent(key);
        if (StringUtils.isNotBlank(pictureVoCache)) {
            // 缓存命中，返回缓存的结果
            return JSONUtil.toBean(pictureVoCache, new TypeReference<>() {
            }, false);
        }
        // 2 本地缓存中没有，查询分布式缓存（Redis）
        pictureVoCache = pictureDataService.getPictureVoCache(key);
        if (StringUtils.isNotBlank(pictureVoCache)) {
            // Redis 命中，存入本地缓存并返回
            LOCAL_CACHE.put(key, pictureVoCache);
            return JSONUtil.toBean(pictureVoCache, new TypeReference<>() {
            }, true);
        }
        // 3 缓存未命中，查询数据库
        PicturePageQuery pageQuery = BeanUtil.copyProperties(request, PicturePageQuery.class);
        PaginationResponse<PictureVo> result = getPictureVoPaginationResponse(pageQuery)
                .filter(pictureVo -> ReviewStatusEnum.APPROVED.equals(pictureVo.getReviewStatus()));
        if (result.isEmpty()) {
            return PaginationResponse.empty(pageQuery.getPageSize(), pageQuery.getPageNum());
        }
        // 更新本地缓存和 Redis缓存
        String cacheValue = JSONUtil.toJsonStr(result);
        LOCAL_CACHE.put(key, cacheValue);
        pictureDataService.savePictureVoCache(key, cacheValue);

        return result;
    }

    @Override
    public PictureVo getPictureById(Long id) {
        ThrowUtils.throwIf(Objects.isNull(id), ErrorCodeEnum.ParamValidError);
        return PictureVo.picturePo2PictureVo(pictureDataService.getPictureById(id)
                .orElseThrow(() -> new BusinessException(ErrorCodeEnum.PictureNotExist)));
    }


    @Override
    public PictureVo getPictureDetail(Long id, Long userId) {
        ThrowUtils.throwIf(Objects.isNull(id), ErrorCodeEnum.ParamValidError);
        PicturePo picturePo = pictureDataService.getPictureById(id)
                .orElseThrow(() -> new BusinessException(ErrorCodeEnum.PictureNotExist));
        Long spaceId = picturePo.getSpaceId();
        if (spaceId != null) {
            // 使用编程式鉴权 —— 如果用注解式鉴权框架会强制校验登陆
            boolean hasPermission = StpKit.SPACE.hasPermission(SpaceUserPermissionConstant.PICTURE_VIEW);
            ThrowUtils.throwIf(!hasPermission, ResultCode.NO_AUTH_ERROR);
//            checkPictureSpaceAuth(picturePo, userId);
        }
        return PictureVo.picturePo2PictureVo(picturePo);
    }

    @Override
    public List<ImageSearchResult> searchPictureByPicture(SearchPictureByPictureRequest request) {
        ThrowUtils.throwIf(Objects.isNull(request.getPictureId()) || request.getPictureId() <= 0, ErrorCodeEnum.ParamValidError);
        Long pictureId = request.getPictureId();
        PicturePo picturePo = pictureDataService.getPictureById(pictureId).orElseThrow(() -> new BusinessException(ErrorCodeEnum.PictureNotExist));
        return ImageSearchApiFacade.searchImage(picturePo.getUrl());
    }

    @Override
    public List<PictureVo> searchPictureByColor(SearchPictureByColorRequest request, Long userId) {
        // 校验权限，已改为注解式鉴权
//        checkSpaceAuth(request.getSpaceId(), userId);
        List<PicturePo> picturePos = pictureDataService.listPicture(request.getSpaceId(), Boolean.FALSE)
                .stream()
                .filter(picturePo -> picturePo.getPicColor() != null && !picturePo.getPicColor().isBlank())
                .toList();
        if (picturePos.isEmpty()) {
            return Collections.emptyList();
        }
        // 目标颜色转化为 Color 对象
        Color targetColor = Color.decode(request.getColor());

        // 计算相似度并排序
        List<PicturePo> sortedPicturePos = picturePos.stream()
                .sorted(Comparator.comparingDouble(picture -> {
                    String picColor = picture.getPicColor();
                    if (picColor.isBlank()) {
                        return Double.MAX_VALUE;
                    }
                    Color pictureColor = Color.decode(picColor);
                    return -ColorSimilarUtils.calculateSimilarity(pictureColor, targetColor);
                }))
                .limit(12)
                .toList();

        return sortedPicturePos.stream()
                .map(PictureVo::picturePo2PictureVo)
                .toList();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PictureVo uploadPicture(Object inputSource, PictureUploadRequest request, Long userId, String userName) {
        // 1. 检验参数
        ThrowUtils.throwIf(Objects.isNull(inputSource), ErrorCodeEnum.ParamValidError, "文件不能为空");
        ThrowUtils.throwIf(!(inputSource instanceof String || inputSource instanceof MultipartFile), ErrorCodeEnum.ParamValidError, "文件格式错误");
        ThrowUtils.throwIf(Objects.isNull(userId), ResultCode.NOT_LOGIN_ERROR);
        // 校验空间
        Long spaceId = request.getSpaceId();
        if (spaceId != null) {
            SpacePo spacePo = spaceDataService.getSpaceById(spaceId)
                    .orElseThrow(() -> new BusinessException(ErrorCodeEnum.DataNotExist, "空间不存在"));
            // 校验空间权限是否为自己
            ThrowUtils.throwIf(!spacePo.getCreateBy().equals(userId), ResultCode.NO_AUTH_ERROR, "没有该空间权限");
            // 校验空间额度
            ThrowUtils.throwIf(spacePo.getTotalSize() > spacePo.getMaxSize(), ResultCode.OPERATION_ERROR, "空间额度不足");
        }

        // 2. 上传图片得到信息
        // 文件路径
        String uploadPathPrefix;
        if (spaceId == null) {
            // 公共空间 + 用户ID
            uploadPathPrefix = String.format("%s/%s", BaseConstant.PUBLIC, userId);
        } else {
            // 私有空间 + 空间ID
            uploadPathPrefix = String.format("%s/%s", BaseConstant.PRIVATE, spaceId);
        }

        // 根据 inputSource 的类型选择上传方式
        PictureUploadTemplate pictureUploadTemplate = (inputSource instanceof String) ? urlPictureUpload : filePictureUpload;
        PictureUploadDto pictureUploadDto = pictureUploadTemplate.uploadPicture(inputSource, uploadPathPrefix);

        // 3. 构造图片实体
        PicturePo picturePo = BeanUtil.copyProperties(pictureUploadDto, PicturePo.class);
        picturePo.setUpdateBy(userId);
        picturePo.setCreateBy(userId);
        picturePo.setCreateName(userName);
        picturePo.setStatus(StatusEnum.Approved);
        // 设置图片名称
        Optional.ofNullable(Objects.requireNonNull(request).getPicName()).ifPresent(picturePo::setName);
        picturePo.setSpaceId(spaceId);

        // 4. 填充审核信息
        fillReviewParams(picturePo, userId);

        // 5. 保存图片，更新空间额度
        try {
            PicturePo po = pictureDataService.save(picturePo);
            ThrowUtils.throwIf(Objects.isNull(po), ErrorCodeEnum.OperationDataError, "保存图片失败");
            if (spaceId != null) {
                SpacePo spacePo = spaceDataService.getSpaceById(spaceId).orElseThrow(() -> new BusinessException(ErrorCodeEnum.DataNotExist, "空间不存在"));
                spacePo.setTotalSize(po.getPicSize() + spacePo.getTotalSize());
                spacePo.setTotalCount(spacePo.getTotalCount() + 1);
                SpacePo saveSpacePo = spaceDataService.save(spacePo);
                ThrowUtils.throwIf(saveSpacePo == null, ErrorCodeEnum.OperationDataError, "更新空间数据失败");
            }
            return PictureVo.picturePo2PictureVo(po);
        } catch (Exception e) {
            // todo 事务失败回滚后，需要删除对象存储中已上传的文件
            throw new BusinessException(ErrorCodeEnum.OperationDataError, "上传图片操作数据库失败");
        }
    }

    @Override
    public Integer batchUploadPicture(PictureBatchUploadRequest request, Long userId, String userName) {
        // 校验参数
        if (Objects.isNull(request.getSearchText())) {
            return 0;
        }
        Integer count = request.getCount();
        String searchText = request.getSearchText();
        ThrowUtils.throwIf(count > 30, ErrorCodeEnum.ParamValidError, "最多只能上传30张图片");
        String namePrefix = request.getNamePrefix();
        if (StringUtils.isBlank(namePrefix)) {
            namePrefix = searchText;
        }

        // 抓取内容
        String fetchUrl = String.format("https://cn.bing.com/images/async?q=%s&mmasync=1", searchText);
        // 解析内容
        Document document;
        try {
            document = Jsoup.connect(fetchUrl).get();
        } catch (IOException e) {
            log.error("抓取内容失败：{}", e.getMessage());
            throw new BusinessException(ResultCode.OPERATION_ERROR, "抓取内容失败");
        }
        Element div = document.getElementsByClass("dgControl").first();
        ThrowUtils.throwIf(Objects.isNull(div), ResultCode.OPERATION_ERROR, "获取元素失败");
//        Elements imgElementList = div.select("img.ming");
        Elements imgElementList = div.select("a.iusc"); // 获取包含完整数据的元素
        int uploadCount = 0;

        for (Element imgElement : imgElementList) {
//            String src = imgElement.attr("src");
            String mAttr = imgElement.attr("m");
            String fileUrl;
            try {
                Map<String, String> mMap = JSONUtil.toBean(mAttr, new TypeReference<>() {
                }, true);
                // 获取murl字段（原始图片URL）
                fileUrl = mMap.get("murl");
            } catch (Exception e) {
                log.error("图片地址解析失败", e);
                continue;
            }
            if (StringUtils.isBlank(fileUrl)) {
                log.debug("当前链接为空，跳过：{}", fileUrl);
                continue;
            }

            int questionMarkIndex = fileUrl.indexOf("?");
            if (questionMarkIndex > -1) {
                fileUrl = fileUrl.substring(0, questionMarkIndex);
            }
            // 上传图片
            PictureUploadRequest pictureUploadRequest = new PictureUploadRequest();
            if (StringUtils.isNotBlank(namePrefix)) {
                pictureUploadRequest.setPicName(namePrefix + "-" + (uploadCount + 1));
            }
            try {
                PictureVo pictureVo = uploadPicture(fileUrl, pictureUploadRequest, userId, userName);
                log.info("上传图片成功：{}", pictureVo);
                uploadCount++;
            } catch (Exception e) {
                log.error("上传图片失败：{}", e.getMessage());
                continue;
            }
            if (uploadCount >= count) {
                break;
            }
        }
        return uploadCount;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deletePicture(DeleteRequest request, Long userId) {
        pictureDataService.getPictureById(request.getId()).ifPresent(picturePo -> {
            // 校验图片的空间权限，已改为注解式鉴权
//            checkPictureSpaceAuth(picturePo, userId);
            picturePo.setUpdateBy(userId);
            picturePo.setDelete(Boolean.TRUE);
            picturePo.setStatus(StatusEnum.Deleted);
            try {
                PicturePo po = pictureDataService.save(picturePo);
                ThrowUtils.throwIf(po == null, ErrorCodeEnum.OperationDataError, "删除图片失败");
                SpacePo spacePo = spaceDataService.getSpaceById(picturePo.getSpaceId()).orElse(null);
                if (spacePo != null) {
                    spacePo.setTotalSize(spacePo.getTotalSize() - picturePo.getPicSize());
                    spacePo.setTotalCount(spacePo.getTotalCount() - 1);
                    SpacePo save = spaceDataService.save(spacePo);
                    ThrowUtils.throwIf(save == null, ErrorCodeEnum.OperationDataError, "更新空间数据失败");
                }
            } catch (Exception e) {
                throw new BusinessException(ErrorCodeEnum.OperationDataError, "删除图片操作数据库失败");
            }
        });
        return Boolean.TRUE;
    }

    @Override
    public Boolean updatePicture(PictureUpdateRequest request, Long userId) {
        pictureDataService.getPictureById(request.getId()).ifPresent(picturePo -> {
            picturePo.setUpdateBy(userId);
            DataUtils.setIfNotNull(request.getName(), picturePo::setName);
            DataUtils.setIfNotNull(request.getDescription(), picturePo::setDescription);
            DataUtils.setIfNotNull(request.getCategory(), picturePo::setCategory);
            DataUtils.setIfNotNull(request.getTagsJson(), picturePo::setTags);
            picturePo.setStatus(request.getStatus() ? StatusEnum.Approved : StatusEnum.Rejected);
            // 填充审核信息
            fillReviewParams(picturePo, userId);
            pictureDataService.save(picturePo);
        });
        return Boolean.TRUE;
    }

    @Override
    public Boolean editPicture(PictureEditRequest request, Long userId) {
        pictureDataService.getPictureById(request.getId()).ifPresent(picturePo -> {
            // 校验图片的空间权限，已改为注解式鉴权
//            checkPictureSpaceAuth(picturePo, userId);
            picturePo.setUpdateBy(userId);
            DataUtils.setIfNotNull(request.getName(), picturePo::setName);
            DataUtils.setIfNotNull(request.getDescription(), picturePo::setDescription);
            DataUtils.setIfNotNull(request.getCategory(), picturePo::setCategory);
            DataUtils.setIfNotNull(request.getTagsJson(), picturePo::setTags);
            // 填充审核信息
            fillReviewParams(picturePo, userId);
            pictureDataService.save(picturePo);
        });
        return Boolean.TRUE;
    }

    @Override
    public PaginationResponse<PictureVo> pageManagerPicture(PictureManagerPageRequest request) {
        PicturePageQuery pageQuery = BeanUtil.copyProperties(request, PicturePageQuery.class);
        pageQuery.setReviewStatus(ReviewStatusEnum.getEnumByValue(request.getReviewStatus()));
        return getPictureVoPaginationResponse(pageQuery);
    }

    @Override
    public Boolean reviewPicture(PictureReviewRequest request, Long userId) {
        ThrowUtils.throwIf(Objects.isNull(request.getId()) || Objects.isNull(request.getReviewStatus()), ErrorCodeEnum.ParamValidError);
        pictureDataService.getPictureById(request.getId()).ifPresent(picturePo -> {
            if (picturePo.getStatus().equals(StatusEnum.Approved) && !request.getReviewStatus().equals(picturePo.getReviewStatus().getValue())) {
                picturePo.setUpdateBy(userId);
                picturePo.setReviewBy(userId);
                picturePo.setReviewTime(LocalDateTime.now());
                picturePo.setReviewStatus(ReviewStatusEnum.getEnumByValue(request.getReviewStatus()));
                Optional.ofNullable(request.getReviewMessage()).ifPresent(picturePo::setReviewMessage);
                pictureDataService.save(picturePo);
                log.info("审核图片成功：{}", picturePo);
            }
        });
        return Boolean.TRUE;
    }

    @Override
    public CreateOutPaintingTaskResponse createPictureOutPaintingTask(CreatePictureOutPaintingTaskRequest request, Long userId) {
        // 获取图片信息
        PicturePo picturePo = pictureDataService.getPictureById(request.getPictureId())
                .orElseThrow(() -> new BusinessException(ErrorCodeEnum.PictureNotExist, "图片不存在"));
        // 校验权限，已改为注解式鉴权
//        checkPictureSpaceAuth(picturePo, userId);
        // 构造请求参数
        CreateOutPaintingTaskRequest.Input input = new CreateOutPaintingTaskRequest.Input();
        input.setImageUrl(picturePo.getUrl());
        CreateOutPaintingTaskRequest taskRequest = new CreateOutPaintingTaskRequest();
        taskRequest.setInput(input);
        BeanUtil.copyProperties(request, taskRequest);
        // 调用API方法创建任务
        return aliYunAiApi.createOutPaintingTask(taskRequest);
    }

    @Override
    public GetOutPaintingTaskResponse getPictureOutPaintingTask(String taskId) {
        ThrowUtils.throwIf(StringUtils.isBlank(taskId), ErrorCodeEnum.ParamValidError, "任务 id 不能为空");
        return aliYunAiApi.getOutPaintingTask(taskId);
    }

    /**
     * 检验图片的空间权限
     *
     * @param picturePo 图片实体
     * @param userId    用户id
     * @author Ma Chengrui
     * @since 2025/9/13
     */
    public void checkPictureSpaceAuth(PicturePo picturePo, Long userId) {
        Long spaceId = picturePo.getSpaceId();
        if (spaceId == null) {
            // 公共空间，仅本人和管理员可以操作
            ThrowUtils.throwIf(!Objects.equals(userId, picturePo.getCreateBy()) && !roleDataService.isAdmin(userId), ResultCode.NO_AUTH_ERROR);
        } else {
            // 私有空间，仅创建者可以操作
            ThrowUtils.throwIf(!Objects.equals(userId, picturePo.getCreateBy()), ResultCode.NO_AUTH_ERROR);
        }
    }

    /**
     * 检验空间权限
     *
     * @param spaceId 图片实体
     * @param userId  用户id
     * @author Ma Chengrui
     * @since 2025/9/13
     */
    public void checkSpaceAuth(Long spaceId, Long userId) {
        SpacePo spacePo = spaceDataService.getSpaceById(spaceId)
                .orElseThrow(() -> new BusinessException(ErrorCodeEnum.SpaceNotExist));
        ThrowUtils.throwIf(!spacePo.getCreateBy().equals(userId), ResultCode.NO_AUTH_ERROR, "没有该空间权限");
    }

    /**
     * 获取图片分页结果
     *
     * @param pageQuery 分页查询条件
     * @author Ma Chengrui
     * @since 2025/7/3
     */
    private PaginationResponse<PictureVo> getPictureVoPaginationResponse(PicturePageQuery pageQuery) {
        PaginationResponse<PicturePo> pageResponse = pictureDataService.pagePicture(pageQuery);
        if (pageResponse.isEmpty()) {
            return PaginationResponse.empty(pageQuery.getPageSize(), pageQuery.getPageNum());
        }
        return pageResponse.map(PictureVo::picturePo2PictureVo);
    }


    /**
     * 填充审核信息
     *
     * @param picture 图片实体
     * @param userId  操作用户id
     * @author Ma Chengrui
     * @since 2025/7/18
     */
    public void fillReviewParams(PicturePo picture, Long userId) {
        if (roleDataService.isAdmin(userId)) {
            // 管理员自动过审
            picture.setReviewStatus(ReviewStatusEnum.APPROVED);
            picture.setReviewBy(userId);
            picture.setReviewMessage("管理员自动过审");
            picture.setReviewTime(LocalDateTime.now());
        } else {
            // 非管理员，创建或编辑都要改为待审核
            picture.setReviewStatus(ReviewStatusEnum.PENDING);
        }
    }

}
